
//
// 110414a- AH - Created.
// 110414b- AH - (Inside) Parameterized quote characters and allowed for more
//               than one.
// 110414c- AH - (Hector) parse(BufferedReader) that parse // style comments
//               and made parse(File) call this so that also parse comments.
// 120321 - AH - Removed @Override for interface method (1.5 compatible).
//

package org.soekrat.engine.pitbul.parser;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * <p>
 *   Breaks character stream into tokens.
 * </p>
 * <p>
 *   Iterable over tokens.
 * </p>
 * @author Arne Halvorsen (AH)
 */
public final class Tokenizer implements Iterable<String>
{
  private final List<String> m_tokens=new ArrayList<String>();
  
  private String m_ignoreChars="";
  
  private String m_quoteChars="\"'";
  
  private String m_tokenChars="";
  
  /**
   * <p>
   *   Constructor.
   * </p>
   */
  public Tokenizer(){}
  
  /**
   * <p>
   *   Sets
   *   {@code String} which characters are quote characters.
   * </p>
   * @param q Quote characters.
   */
  public void setQuoteChars(String q)
  {
    if (q==null)
    {
      throw new NullPointerException(q);
    }
    
    m_quoteChars=q;
  }
  
  /**
   * <p>
   *   Gets
   *   {@code String} which characters are quote characters.
   * </p>
   * @return Quote characters.
   */
  public String getQuoteChars(){ return m_quoteChars; }
  
  /**
   * <p>
   *   Sets
   *   {@code String} which characters are to be recognized as tokens.
   * </p>
   * @param c Token characters. 
   */
  public void setTokenChars(String c)
  {
    if (c==null)
    {
      throw new NullPointerException("c");
    }
    
    m_tokenChars=c;
  }
  
  /**
   * <p>
   *   Gets
   *   {@code String} which characters are to be recognized as tokens.
   * </p>
   * @return Token characters.
   */
  public String getTokenChars(){ return m_tokenChars; }
  
  /**
   * <p>
   *   Sets
   *   {@code String} which characters are to be treated as white space.
   * </p>
   * @param c Token characters. 
   */
  public void setIgnoreChars(String c)
  {
    if (c==null)
    {
      throw new NullPointerException("c");
    }
    
    m_ignoreChars=c;
  }
  
  /**
   * <p>
   *   Gets
   *   {@code String} which characters are to be treated as white space.
   * </p>
   * @return Token characters.
   */
  public String getIgnoreChars(){ return m_ignoreChars; }
  
  /**
   * <p>
   *   Gets number of tokens this has at the moment.
   * </p>
   * @return Count.
   */
  public int getTokenCount(){ return m_tokens.size(); }

  /**
   * <p>
   *   Gets token.
   * </p>
   * @param idx Index to token to get.
   * @return Token.
   * @throws IndexOutOfBoundsException If {@code idx<0 || idx>=getTokenCount()}.
   */
  public String getToken(int idx){ return m_tokens.get(idx); }
  
  // Declared in Iterable<String>.
  public Iterator<String> iterator(){ return m_tokens.iterator(); }
  
  /**
   * <p>
   *   Clears read tokens and make ready for new parsing.
   * </p>
   * <p>
   *   Characters registered as token characters is left as is.
   * </p>
   */
  public void clear(){ m_tokens.clear(); m_sb.setLength(0); }
  
  /**
   * <p>
   *   Parse 
   *   {@code String}.
   * </p> 
   * @param s {@code String} to parse.
   */
  public void parse(String s)
  {
    if (s==null)
    {
      throw new NullPointerException("s");
    }
    
    int n=s.length();
    for (int i=0; i<n; i++) put(s.charAt(i));
    end();
  }
  
  public void parse(File f) throws IOException
  {
    if (f==null)
    {
      throw new NullPointerException("f");
    }
    
    FileReader fr=null;
    try
    {
      fr=new FileReader(f);
      parse(new BufferedReader(fr));
    }
    finally
    {
      if (fr!=null) try { fr.close(); } catch (IOException _){}
    }
  }
  
  public void parse(Reader r) throws IOException
  {
    if (r==null)
    {
      throw new NullPointerException("r");
    }
    
    char[] buf=new char[1024];
    int n;
    while ((n=r.read(buf))!=-1) put(buf, 0, n);
    end();
  }
  
  public void parse(BufferedReader br) throws IOException
  {
    if (br==null)
    {
      throw new NullPointerException("br");
    }
    
    String l=br.readLine();
    while (l!=null)
    {
      l=l.trim();
      if (l.length()>0 && !l.startsWith("//")) parse(l);
      l=br.readLine();
    }
  }
  
  private StringBuilder m_sb=new StringBuilder();
  
  private boolean quote=false;
  
  /**
   * 
   * <p>
   *   Pu
   * </p>
   * @param c
   */
  public void put(char[] c)
  {
    if (c==null)
    {
      throw new NullPointerException("c");
    }
    
    put(c, 0, c.length);
  }
  
  /**
   * <p>
   *   Puts character from the stream.
   * </p>
   * @param c      Characters to put.
   * @param offset Position to start from in {@code c}.
   */
  public void put(char[] c, int offset, int len)
  {
    if (c==null)
    {
      throw new NullPointerException("c");
    }
    
    for (int i=offset; i<len; i++) put(c[i]);
  }
  
  /**
   * <p>
   *   Puts a character from the stream.
   * </p>
   * @param c Character to put.
   */
  public void put(char c)
  {
    if (isQuoteChar(c))
    {
      if (quote)
      {
        String t=m_sb.toString();
        m_sb.setLength(0);
        m_tokens.add(t);
        quote=false;
      }
      else
      {
        String t=m_sb.toString();
        m_sb.setLength(0);
        if (t.length()>0) m_tokens.add(t);
        quote=true;
      }
    }
    else if (quote)
    {
      m_sb.append(c);
    }
    else if (!Character.isWhitespace(c) && !toIgnore(c))
    {
      if (isTokenChar(c))
      {
        String t=m_sb.toString();
        m_sb.setLength(0);
        if (t.length()>0) m_tokens.add(t);
        m_tokens.add(""+c);
      }
      else
      {
        m_sb.append(c);
      }
    }
    else
    {
      if (m_sb.length()>0)
      {
        String t=m_sb.toString();
        m_sb.setLength(0);
        m_tokens.add(t);
      }
    }
  }
  
  public void end()
  {
    if (m_sb.length()>0)
    {
      String t=m_sb.toString();
      m_sb.setLength(0);
      m_tokens.add(t);
    }
  }
  
  public boolean toIgnore(char c)
  {
    int n=m_ignoreChars.length();
    for (int i=0; i<n; i++) if (c==m_ignoreChars.charAt(i)) return true;
    return false;
  }
  
  public boolean isQuoteChar(char c)
  {
    int n=m_quoteChars.length();
    for (int i=0; i<n; i++) if (c==m_quoteChars.charAt(i)) return true;
    return false;
  }
  
  public boolean isTokenChar(char c)
  {
    int n=m_tokenChars.length();
    for (int i=0; i<n; i++) if (c==m_tokenChars.charAt(i)) return true;
    return false;
  }
  
  @Override
  public String toString(){ return m_tokens.toString(); }
  
  public static void main(String[] args) 
  {
    String s="{a 123 45 {'arne is rat' yup}6789 10}";
    Tokenizer t=new Tokenizer();
    t.setTokenChars("{}");
    t.parse(s);
    System.out.println(t);
  }
  
}
